﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Infrastructure.Aspect.Events
{
    /// <summary>
    /// Delegate garbage collector release 
    /// </summary>
    /// <typeparam name="TDelegate"></typeparam>
    public class WeakDelegate<TDelegate> where TDelegate :class    
    {
        List<WeakReference> _targets = new List<WeakReference>();
        public WeakDelegate()
        {
            if (!typeof(TDelegate).IsSubclassOf(typeof(Delegate)))
                throw new InvalidOperationException("TDelegate must be a delegate type");
        }
        public void Combine(TDelegate target)
        {
            if (target == null) return;
            foreach (Delegate d in (target as Delegate).GetInvocationList())
                _targets.Add(new WeakReference(d));
        }
        public void Remove(TDelegate target)
        {
            if (target == null) return;
            foreach (Delegate d in (target as Delegate).GetInvocationList())
            {
                WeakReference weak = _targets.Find(w => d.Equals(w.Target));
                if (weak != null) _targets.Remove(weak);
            }
        }
        public TDelegate Target
        {
            get
            {
                var deadRefs = new List<WeakReference>();
                Delegate combinedTarget = null;
                foreach (WeakReference weak in _targets)
                {
                    Delegate target = (Delegate)weak.Target;
                    if (target != null)
                        combinedTarget = Delegate.Combine(combinedTarget, target);
                    else
                        deadRefs.Add(weak);
                }
                    foreach (WeakReference weak in deadRefs) // Remove dead references
                        _targets.Remove(weak); // from _targets.
                    return combinedTarget as TDelegate;
            }
            set
            {
                _targets.Clear();
                Combine(value);
            }
        }
    }
}
